//
// Created by itworker on 23-5-31.
//
#include "../include/nat_client.h"

UserList    NATClient::m_uClntList;
int         NATClient::m_iClntSock = -1;
bool        NATClient::m_bRecvACK;


NATClient::NATClient() : m_sClientPort(9192), m_sServerPort(SERVER_PORT)
{
    //TO DO...
    std::strcpy(m_sUserName, "client_");
    std::strcpy(m_sServerIP, "192.168.1.201");
}

NATClient::~NATClient()
{
    command_exit();
    std::this_thread::sleep_for(std::chrono::seconds(100));
}

void NATClient::init()
{
    m_iClntSock = create_socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    bind_socket(m_iClntSock);

    std::cout << "Please input username: ";
    std::cin >> m_sUserName;
    connect_server(m_iClntSock, m_sUserName, m_sServerIP);
}

int NATClient::create_socket(int domain, int type, int protocol)
{
    int sock = socket(domain, type, protocol);
    if (sock == -1)
    {
        SockErrorHandle("create socket error.");
    }
    return sock;
}

void NATClient::bind_socket(int sock)
{
    sockaddr_in local_addr;
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    local_addr.sin_port = htons(m_sClientPort);
    try {
        bind(sock, (sockaddr*)&local_addr, sizeof(local_addr));
    }
    catch (...) {
        SockErrorHandle("bind socket error.");
    }
}

void NATClient::connect_server(int sock, char *username, char *serv_ip)
{
    sockaddr_in remote_addr;
    memset(&remote_addr, 0, sizeof(remote_addr));
    remote_addr.sin_family = AF_INET;
    remote_addr.sin_addr.s_addr = inet_addr(serv_ip);
    remote_addr.sin_port = htons(m_sServerPort);

    cltMessage sendBuf;
    sendBuf.iMessageType = LOGIN;
    std::strcpy(sendBuf.message.cltLoginMsg.username, username);

    //向服务器发送的登陆请求
    sendto(sock, (const char*)&sendBuf, sizeof(sendBuf), 0, (sockaddr*)&remote_addr, sizeof(remote_addr));

    int usercount;
    socklen_t remote_addr_size = sizeof(remote_addr);
    while (true)
    {
        fd_set readfds;
        fd_set writfds;

        FD_ZERO(&readfds);
        FD_ZERO(&writfds);

        FD_SET(sock, &readfds);

        int maxfd = sock;
        timeval to;
        to.tv_sec = 2;
        to.tv_usec = 0;

        int n = select(maxfd + 1, &readfds, &writfds, NULL, &to);
        if (n < 0) //发生错误
        {
            SockErrorHandle("login: select socket error.");
        }
        if (n == 0)//超时
        {
            sendto(sock, (const char*)&sendBuf, sizeof(sendBuf), 0, (sockaddr*)&remote_addr, sizeof(remote_addr));
        }
        //发生事件
        if (FD_ISSET(sock, &readfds))
        {
            ssize_t msg_len = recvfrom(sock, (char*)&usercount, sizeof(usercount), 0, (sockaddr*)&remote_addr, &remote_addr_size);
            if (msg_len <= 0)
            {
                std::cout << "login: recv message error." << std::endl;
            }
            break;
        }
    }

    //登陆服务器之后，接收服务器发来的所有登陆的客户端信息
    std::cout << "Have " << usercount << " client users login server:" << std::endl;
    for (int i = 0; i < usercount; ++i) {
        cltUserNode* node = new cltUserNode;
        ssize_t msg_len = recvfrom(sock, (char*)node, sizeof(cltUserNode), 0, (sockaddr*)&remote_addr, &remote_addr_size);
        if (msg_len <= 0)
        {
            std::cout << "login: recv message error." << std::endl;
            break;
        }
        m_uClntList.push_back(node);
        std::cout << "username: " << node->username;
        in_addr temp;
        temp.s_addr = htonl(node->ip);
        std::cout << " < " << inet_ntoa(temp) << " : " << node->port << " >" << std::endl;
    }
}

void NATClient::output_usage()
{
    std::cout << "You can input you command:" << std::endl;
    std::cout << "Command Type: send, tell, exit, getu" << std::endl;
    std::cout << "Example : send Username Message" << std::endl;
    std::cout << "Example : tell Username ip:port Message" << std::endl;
    std::cout << "          exit" << std::endl;
    std::cout << "          getu" << std::endl;
    std::cout << std::endl;
}

void NATClient::ParseCommand(char *commandLine)
{
    if (std::strlen(commandLine) < 4)
    {
        return;
    }

    char command[10];
    strncpy(command, commandLine, 4);
    command[4]='\0';

    if (strcmp(command, "exit") == 0)
    {
        command_exit();
    }
    else if (strcmp(command, "send") == 0)
    {
        command_send(commandLine);
    }
    else if (strcmp(command, "tell") == 0)
    {
        command_tell(commandLine);
    }
    else if (strcmp(command, "getu") == 0)
    {
        command_getu();
    }
}

void NATClient::RecvThreadProc()
{
    sockaddr_in remote_addr;
    socklen_t remote_addr_size = sizeof(remote_addr);
    cltP2PMessage recvBuf;

    while (true)
    {
        ssize_t str_len = recvfrom(m_iClntSock, (char*)&recvBuf, sizeof(recvBuf), 0, (sockaddr*)&remote_addr, &remote_addr_size);
        if (str_len <= 0)
        {
            continue;
        }

        switch (recvBuf.iMessageType) {
            case P2PMESSAGE:
            {
                //接收到P2P的消息
                NATClient::command_P2PRecvMessageHandle(remote_addr, recvBuf, remote_addr_size);
                break;
            }
            case P2PSOMEONEWANTTOCALLYOU:
            {
                NATClient::command_P2PSomeoneWantToCallYouHandle(remote_addr, recvBuf, remote_addr_size);
                break;
            }
            case P2PMESSAGEACK:
            {
                //发送消息的应答
                m_bRecvACK = true;

                std::cout << "Receive message ACK from: ";
                std::cout << "< " << inet_ntoa(remote_addr.sin_addr);
                std::cout << " : " << ntohs(remote_addr.sin_port) << " >" << std::endl;
                break;
            }
            case P2PTRASH:
            {
                // 对方发送的打洞消息，忽略掉。
                //do nothing ...
                std::cout << "Receive P2P trash data from: ";
                std::cout << "< " << inet_ntoa(remote_addr.sin_addr);
                std::cout << " : " << ntohs(remote_addr.sin_port) << " >" << std::endl;
                break;
            }
            case GETALLUSER:
            {
                int usercount;
                socklen_t from_size = sizeof(remote_addr);
                ssize_t str_len = recvfrom(m_iClntSock, (char*)&usercount, sizeof(int), 0, (sockaddr*)&remote_addr, &from_size);
                if (str_len <= 0)
                {
                    std::cout << "Login error." << std::endl;
                }

                m_uClntList.clear();

                std::cout << "Have " << usercount << " client users logined server." << std::endl;
                for (int i = 0; i < usercount; ++i) {
                    cltUserNode *node = new cltUserNode;
                    recvfrom(m_iClntSock, (char*)node, sizeof(cltUserNode), 0, (sockaddr*)&remote_addr, &from_size);
                    m_uClntList.push_back(node);
                    std::cout << "Username: " << node->username << std::endl;
                    in_addr tmp;
                    tmp.s_addr = htonl(node->ip);
                    std::cout << "UserIP: " << inet_ntoa(tmp) << std::endl;
                    std::cout << "UserPORT: " << node->port << std::endl;
                    std::cout << std::endl;
                }
                break;
            }
        }
    }
}

bool NATClient::SendMessageTo_v1(char *username, char *message)
{
    char realMessage[256];
    unsigned int userIP;
    unsigned short userPORT;
    bool findUser = false;

    for (UserList::iterator usrIter = m_uClntList.begin(); usrIter != m_uClntList.end(); ++usrIter)
    {
        if (strcmp((*usrIter)->username, username) == 0)
        {
            userIP = (*usrIter)->ip;
            userPORT = (*usrIter)->port;
            findUser = true;
        }
    }

    if (!findUser)
    {
        return false;
    }

    std::strcpy(realMessage, message);
    for (int i = 0; i < MAXRETRY; ++i)
    {
        m_bRecvACK = false;

        sockaddr_in remote_addr;
        memset(&remote_addr, 0, sizeof(remote_addr));
        remote_addr.sin_family = AF_INET;
        remote_addr.sin_addr.s_addr = htonl(userIP);
        remote_addr.sin_port = htons(userPORT);

        cltP2PMessage sendBuf;
        sendBuf.iMessageType = P2PMESSAGE;
        sendBuf.iStringLen = (int) strlen(realMessage) + 1;

        sendto(m_iClntSock, (const char*)&sendBuf, sizeof(sendBuf), 0, (sockaddr*)&remote_addr, sizeof(remote_addr));
        sendto(m_iClntSock, (const char*)realMessage, sendBuf.iStringLen, 0, (sockaddr*)&remote_addr, sizeof(remote_addr));

        // 等待接收线程将此标记修改
        for (int j = 0; j < 10; ++j) {
            if (m_bRecvACK)
            {
                return true;
            }
            std::this_thread::sleep_for(std::chrono::seconds(300));
        }

        // 没有接收到目标主机的回应，认为目标主机的端口映射没有
        // 打开，那么发送请求信息给服务器，要服务器告诉目标主机
        // 打开映射端口（UDP打洞）
        sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr(m_sServerIP);
        server.sin_port = htons(m_sServerPort);

        cltMessage cltMsgToSer;
        cltMsgToSer.iMessageType = P2PTRANS;
        std::strcpy(cltMsgToSer.message.cltTransMsg.username, username);

        sendto(m_iClntSock, (const char*)&cltMsgToSer, sizeof(cltMsgToSer), 0, (sockaddr*)&server, sizeof(server));
        std::this_thread::sleep_for(std::chrono::seconds(100)); // 等待对方先发送信息。
    }
    return false;
}

bool NATClient::SendMessageTo_v2(char *username, char *message, const char *sIP, u_short nPort)
{
    char realMessage[256];
    unsigned int userIP = 0L;
    unsigned short userPORT = 0;
    bool findUser = false;

    if (sIP != NULL)
    {
        userIP = ntohl(inet_addr(sIP));
        userPORT = nPort;
    }
    else
    {
        for (UserList::iterator usrIter = m_uClntList.begin(); usrIter != m_uClntList.end(); ++usrIter)
        {
            if (strcmp((*usrIter)->username, username) == 0)
            {
                userIP = (*usrIter)->ip;
                userPORT = (*usrIter)->port;
                findUser = true;
            }
        }

        if (!findUser)
        {
            return false;
        }
    }

    std::strcpy(realMessage, message);

    sockaddr_in remote_addr;
    memset(&remote_addr, 0, sizeof(remote_addr));
    remote_addr.sin_family = AF_INET;
    remote_addr.sin_addr.s_addr = htonl(userIP);
    remote_addr.sin_port = htons(userPORT);

    cltP2PMessage sendBuf;
    sendBuf.iMessageType = P2PMESSAGE;
    sendBuf.iStringLen = (int) strlen(realMessage) + 1;

    std::cout << "Send a message to: ";
    std::cout << "< " << inet_ntoa(remote_addr.sin_addr);
    std::cout << " : " << ntohs(remote_addr.sin_port);
    std::cout << " > --- [" << realMessage << "]" << std::endl;

    for (int i = 0; i < MAXRETRY; ++i) {
        m_bRecvACK = false;
        sendto(m_iClntSock, (const char*)&sendBuf, sizeof(sendBuf), 0, (sockaddr*)&remote_addr, sizeof(remote_addr));
        sendto(m_iClntSock, (const char*)realMessage, sendBuf.iStringLen, 0, (sockaddr*)&remote_addr, sizeof(remote_addr));

        //等待接收线程将此标记修改
        for (int j = 0; j < 10; ++j) {
            if (m_bRecvACK)
            {
                return true;
            }
            std::this_thread::sleep_for(std::chrono::seconds(300));
        }
    }
    return false;
}

void NATClient::command_exit()
{
    //登出消息
    cltMessage sendBuf;
    sendBuf.iMessageType = LOGOUT;
    std::strcpy(sendBuf.message.cltLogoutMsg.username, m_sUserName);

    sockaddr_in remote_addr;
    remote_addr.sin_family = AF_INET;
    remote_addr.sin_addr.s_addr = inet_addr(m_sServerIP);
    remote_addr.sin_port = htons(m_sServerPort);

    sendto(m_iClntSock, (const char*)&sendBuf, sizeof(sendBuf), 0, (sockaddr*)&remote_addr, sizeof(remote_addr));

    shutdown(m_iClntSock, 2);   //同时断开IO流，无法传输和接收数据
    close(m_iClntSock);
    exit(0);
}

void NATClient::command_getu()
{
    int command = GETALLUSER;
    sockaddr_in remote_addr;
    memset(&remote_addr, 0, sizeof(remote_addr));
    remote_addr.sin_family = AF_INET;
    remote_addr.sin_addr.s_addr = inet_addr(m_sServerIP);
    remote_addr.sin_port = htons(m_sServerPort);
    sendto(m_iClntSock, (const char*)&command, sizeof(command), 0, (sockaddr*)&remote_addr, sizeof(remote_addr));
}

void NATClient::command_send(char *commandLine)
{
    char sendName[20];
    char message[COMMANDMAXC];

    int i = 5;
    while (true)
    {
        if (commandLine[i] != ' ')
        {
            sendName[i - 5] = commandLine[i];
        }
        else
        {
            sendName[i - 5] = '\0';
            break;
        }

        i++;
    }

    std::strcpy(message, &(commandLine[i + 1]));
    if (SendMessageTo_v1(sendName, message))
    {
        std::cout << "Send OK!" << std::endl;
    }
    else
    {
        std::cout << "Send Failure!" << std::endl;
    }
}

void NATClient::command_tell(char *commandLine)
{
    char sendName[20];
    char sendTo[64] = {0};
    char message[COMMANDMAXC];

    int i = 5;
    while (true)
    {
        if (commandLine[i] != ' ')
        {
            sendName[i - 5] = commandLine[i];
        }
        else
        {
            sendName[i - 5] = '\0';
            break;
        }

        i++;
    }

    i++;
    int nStart = i;
    while (true)
    {
        if (commandLine[i] != ' ')
        {
            sendName[i - nStart] = commandLine[i];
        }
        else
        {
            sendName[i - nStart] = '\0';
            break;
        }

        i++;
    }

    std::strcpy(message, &(commandLine[i + 1]));

    char szIP[32] = {0};
    char *p1 = sendTo;
    char *p2 = szIP;
    while ( *p1 != ':' )
    {
        *p2++ = *p1++;
    }

    p1++;
    u_short nPort = atoi( p1 );

    if(SendMessageTo_v2(sendName, message, strcmp(szIP, "255.255.255.255") ? szIP : NULL, nPort))
    {
        std::cout << "Send OK!" << std::endl;
    }
    else
    {
        std::cout << "Send Failure!" << std::endl;
    }
}

void NATClient::command_P2PRecvMessageHandle(sockaddr_in &remote_addr, cltP2PMessage &recvBuf, socklen_t sinlen)
{
    char* recvMsg = new char[recvBuf.iStringLen];
    ssize_t str_len = recvfrom(m_iClntSock, recvMsg, sizeof(recvMsg), 0, (sockaddr*)&remote_addr, &sinlen);
    if (str_len <= 0)
    {
        std::cout << "P2P: receive message error." << std::endl;
    }

    recvMsg[str_len - 1] = '\0';
    std::cout << "Receive a message: ";
    std::cout << "< " << inet_ntoa(remote_addr.sin_addr);
    std::cout << " : " << ntohs(remote_addr.sin_port);
    std::cout << " > --- [" << recvMsg << "]" << std::endl;

    cltP2PMessage sendBuf;
    sendBuf.iMessageType = P2PMESSAGEACK;
    sendto(m_iClntSock, (const char*)&sendBuf, sizeof(sendBuf), 0, (sockaddr*)&remote_addr, sizeof(remote_addr));

    std::cout << "Send a message ACK to: ";
    std::cout << "< " << inet_ntoa(remote_addr.sin_addr);
    std::cout << " : " << ntohs(remote_addr.sin_port) << " >" << std::endl;
}

void NATClient::command_P2PSomeoneWantToCallYouHandle(sockaddr_in &remote_addr, cltP2PMessage &recvBuf, socklen_t sinlen)
{
    std::cout << "Receive P2P someone want to call you from: ";
    std::cout << "< " << inet_ntoa(remote_addr.sin_addr);
    std::cout << " : " << ntohs(remote_addr.sin_port) << " >" << std::endl;

    sockaddr_in _remote_addr;
    memset(&_remote_addr, 0, sizeof(_remote_addr));
    _remote_addr.sin_family = AF_INET;
    _remote_addr.sin_addr.s_addr = htonl(recvBuf.iStringLen);
    _remote_addr.sin_port = htons(recvBuf.Port);

    //UDP hole punching
    cltP2PMessage sendBuf;
    sendBuf.iMessageType = P2PTRASH;
    sendto(m_iClntSock, (const char*)&sendBuf, sizeof(sendBuf), 0, (sockaddr*)&_remote_addr, sizeof(_remote_addr));

    std::cout << "Send P2P trash to: ";
    std::cout << "< " << inet_ntoa(_remote_addr.sin_addr);
    std::cout << " : " << ntohs(_remote_addr.sin_port) << " >" << std::endl;
}